﻿using Dapper;
using System;
using System.Linq;
using System.Text;
using EaseDapper.Abstract;
using EaseDapper.Constants;
using EaseDapper.Interface;
using EaseDapper.Utilities;
using System.Threading.Tasks;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using EaseDapper.Providers.ExpressionContentProvider;

namespace EaseDapper
{
    public class MySqlQueryExpressions<T> : IQueryExpressions<T> where T : class
    {
        private readonly List<string> WhereStringList = new List<string>();
        private static readonly object obj_content_lock = new object();
        private ContentProvider _content;  //未静态
        public ContentProvider Content
        {
            get
            {
                if (_content is null)
                {
                    lock (obj_content_lock)
                    {
                        _content = new ContentProvider();
                    }
                }
                return _content;
            }
        }

        private static string TableName { get; set; }
        protected StringBuilder SqlWhereString { get; set; }
        protected string GroupByString { get; set; }
        protected string HavingString { get; set; }
        protected string OrderByString { get; set; }
        protected bool EnableLimit { get; set; }
        protected int LimitValue { get; set; }

        public MySqlQueryExpressions()
        {
            var tableName = typeof(T).Name;
            var tableattr = typeof(T).GetCustomAttributes(true).SingleOrDefault(attr => attr.GetType().Name == typeof(TableAttribute).Name) as dynamic;
            if (tableattr != null)
            {
                tableName = tableattr.Name;
            }
            TableName = tableName;
            SqlWhereString = new StringBuilder();
        }

        public IQueryExpressions<T> Where(Expression<Func<T, bool>> expression)
        {
            AppendWhereString(Content.Builder(expression.Body, QueryType.WHERE));
            return this;
        }

        public IQueryExpressions<T> With<In>(Expression<Func<T, object>> expression1, Expression<Func<In, object>> expression2, IQueryExpressions<In> query, WithPattern type = WithPattern.In) where In : class
        {
            StringBuilder sqlStr = new StringBuilder();
            sqlStr.Append(Content.Builder(expression1.Body, QueryType.WHERE));
            sqlStr.Append(type switch
            {
                WithPattern.In => " In ",
                WithPattern.Equals => " = ",
                _ => " In ",
            });
            sqlStr.Append('(');
            string inQuery = query.ToString().Replace("*", Content.Builder(expression2.Body, QueryType.WHERE));
            sqlStr.Append(inQuery);
            sqlStr.Append(')');
            AppendWhereString(sqlStr);
            return this;
        }

        public IQueryExpressions<T> Between(Expression<Func<T, object>> expression, object smallerValue, object largerValue)
        {
            string betweenValue = string.Format(Templates.SqlBetweenString, Content.Builder(expression.Body, QueryType.WHERE), smallerValue, largerValue);
            AppendWhereString(betweenValue);
            return this;
        }

        public IQueryExpressions<T> Exist<In>(IQueryExpressions<In> query, ExistPattern type = ExistPattern.Exist) where In : class
        {
            string existPrefix = type switch
            {
                ExistPattern.None => "NOT EXISTS",
                _ => "EXISTS",
            };
            string checkExistValue = query.ToString().Replace("*", "1");
            string existValue = string.Format(Templates.SqlExistString, existPrefix, checkExistValue);
            AppendWhereString(existValue);
            return this;
        }

        public IQueryExpressions<T> GroupBy(Expression<Func<T, object>> expression)
        {
            GroupByString = string.Format(Templates.SqlGroupByString, Content.Builder(expression.Body, QueryType.WHERE));
            return this;
        }

        public IQueryExpressions<T> OrderBy(Expression<Func<T, object>> expression, OrderPattern type = OrderPattern.Asc)
        {
            string orderType = type switch
            {
                OrderPattern.Desc => "DESC",
                _ => "ASC",
            };
            OrderByString = string.Format(Templates.SqlOrderByString, Content.Builder(expression.Body, QueryType.WHERE), orderType);
            return this;
        }

        public IQueryExpressions<T> Having(Expression<Func<T, bool>> expression)
        {
            HavingString = string.Format(Templates.SqlHavingString, Content.Builder(expression.Body, QueryType.WHERE));
            return this;
        }

        public IQueryExpressions<T> Or(params Expression<Func<T, bool>>[] expressions)
        {
            StringBuilder sqlStr = new StringBuilder();
            for (int i = 0; i < expressions.Length; i++)
            {
                if (i > 0)
                {
                    sqlStr.Append(" OR ");
                }
                sqlStr.Append("(");
                string buildItem = Content.Builder(expressions[i].Body, QueryType.WHERE);
                sqlStr.Append(buildItem);
                sqlStr.Append(")");
            }
            AppendWhereString(sqlStr);
            return this;
        }

        public IQueryExpressions<T> Take(int value)
        {
            EnableLimit = true;
            LimitValue = value;
            return this;
        }

        public T First()
        {
            try
            {
                Take(1);
                lock (SqlWhereString)
                {
                    T t = default;
                    DapperExecute.Instance.ExecuteWithoutTransaction((conn) =>
                    {
                        t = conn.QueryFirst<T>(this.ToString());
                    });
                    return t;
                }
            }
            catch
            {
                return default(T);
            }
        }

        public async Task<T> FirstAsync()
        {
            return await Task.Factory.StartNew(() =>
            {
                try
                {
                    Take(1);
                    lock (SqlWhereString)
                    {
                        T t = default;
                        DapperExecute.Instance.ExecuteWithoutTransaction((conn) =>
                        {
                            t = conn.QueryFirst<T>(ToString());
                        });
                        return t;
                    }
                }
                catch
                {
                    return default(T);
                }
            });
        }

        public List<T> ToList()
        {
            lock (SqlWhereString)
            {
                IEnumerable<T> list = default;
                DapperExecute.Instance.ExecuteWithoutTransaction((conn) =>
                {
                    list = conn.Query<T>(ToString());
                });
                return list.ToList();
            }
        }

        public async Task<List<T>> ToListAsync()
        {
            return await Task.Factory.StartNew(() =>
            {
                lock (SqlWhereString)
                {
                    IEnumerable<T> list = default;
                    DapperExecute.Instance.ExecuteWithoutTransaction((conn) =>
                    {
                        list = conn.Query<T>(ToString());
                    });
                    return list.ToList();
                }
            });
        }

        public bool Any()
        {
            return ToList().Any();
        }

        private void AppendWhereString(object value)
        {
            if (SqlWhereString is null)
            {
                SqlWhereString = new StringBuilder();
            }
            if (SqlWhereString.Length.Equals(0))
            {
                SqlWhereString.Append(" WHERE ");
            }
            else if (SqlWhereString.Length > 0)
            {
                SqlWhereString.Append(" AND ");
            }
            SqlWhereString.Append(value);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(string.Format(Templates.SqlHeadString, TableName ?? typeof(T).Name));
            if (SqlWhereString != null && SqlWhereString.Length > 0)
            {
                sb.Append(SqlWhereString);
            }
            if (GroupByString.NotNull())
            {
                sb.Append(GroupByString);
            }
            if (HavingString.NotNull())
            {
                sb.Append(HavingString);
            }
            if (OrderByString.NotNull())
            {
                sb.Append(OrderByString);
            }
            if (EnableLimit)
            {
                sb.Append(string.Format(Templates.SqlLimitString, LimitValue));
            }
            return sb.ToString();
        }
    }
}
